package yunquan.wang.base.dao.springdata;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;

import org.springframework.data.jpa.domain.Specification;

@SuppressWarnings(value = { "unchecked", "rawtypes" })
public class RepositoryUtils {
	// EQ, LIKE, LT, GT, LE, GE,NOT,IN,SQL;
	/**
	 * 相等
	 * 
	 * @param <T>
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification eq(final String propertyName, final Object value) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				return cb.equal(toExpressionRecursively(root, propertyName),
						value);
			}
		};
	}

	/**
	 * Exist 子查询
	 * 
	 * @param propertyName
	 * @param value
	 * @param clazz
	 * @return
	 */
	public static Specification exist(final String propertyName,
			final Object value, final Class clazz) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				// 子查询
				Subquery subquery = query.subquery(clazz);
				Root fromProject = subquery.from(clazz);
				subquery.select(fromProject.get("id"));
				subquery.where(cb.equal(
						toExpressionRecursively(fromProject, propertyName),
						value));
				return cb.exists(subquery);

			}
		};
	}

	/**
	 * 相等
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification ne(final String propertyName, final Object value) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				return cb.notEqual(toExpressionRecursively(root, propertyName),
						value);
			}
		};
	}

	/**
	 * 抓取关联属性
	 * 
	 * @param porpertyName
	 * @return
	 */
	public static Specification fetch(final String porpertyName) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				root.fetch(porpertyName);
				return null;
			}
		};
	}

	/**
	 * 模糊查找
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification like(final String propertyName,
			final Object value) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				return cb.like(toExpressionRecursively(root, propertyName),
						String.format("%%%s%%", value));
			}
		};
	}

	/**
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification gt(final String propertyName,
			final Comparable value) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				Expression exp = toExpressionRecursively(root, propertyName);
				return cb.greaterThan(exp, value);
			}
		};
	}

	/**
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification lt(final String propertyName,
			final Comparable value) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				return cb.lessThan(toExpressionRecursively(root, propertyName),
						value);
			}
		};
	}

	/**
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification ge(final String propertyName,
			final Comparable value) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				return cb.greaterThanOrEqualTo(
						toExpressionRecursively(root, propertyName), value);
			}
		};
	}

	/**
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification le(final String propertyName,
			final Comparable value) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				return cb.lessThanOrEqualTo(
						toExpressionRecursively(root, propertyName), value);
			}
		};
	}

	/**
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification notin(final String propertyName,
			final Object value) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				Expression exp = toExpressionRecursively(root, propertyName);
				return cb.isNotMember(value, exp);
			}
		};
	}

	/**
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification in(final String propertyName, final Object value) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				Expression exp = toExpressionRecursively(root, propertyName);
				return cb.isMember(value, exp);
			}
		};
	}

	/**
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification isnull(final String propertyName) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				Expression exp = toExpressionRecursively(root, propertyName);
				return cb.isNull(exp);
			}
		};
	}

	/**
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public static Specification notnull(final String propertyName) {
		return new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query,
					CriteriaBuilder cb) {
				Expression exp = toExpressionRecursively(root, propertyName);
				return cb.isNotNull(exp);
			}
		};
	}

	/**
	 * 解决多级级联的问题
	 * 
	 * @param path
	 * @param propertyName
	 * @return
	 */

	private static <T> Expression<T> toExpressionRecursively(Path<T> path,
			String propertyName) {
		String[] props = propertyName.split("\\.");
		Path<T> result = (Path<T>) path.get(props[0]);
		if (props.length > 1) {
			return toExpressionRecursively(result, props[1]);
		} else {
			return result;
		}
	}
}
